package com.ruoyi.common.core.redis;

import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * MybatisRedisCache 缓存工具类
 * Title: MybatisRedisCache
 * Description:
 *
 * @author charles
 */
@Slf4j
public class RelativeCache implements Cache {

    private RedisTemplate<String, Object> redisTemplate;

    private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间

    private final List<RelativeCache> relations = new ArrayList<>();

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

    private final String id;
    private final Class<?> mapperClass;
    private boolean clearing;

    public RelativeCache(String id) throws Exception {
        this.id = id;
        this.mapperClass = Class.forName(id);
        RelativeCacheContext.putCache(mapperClass, this);
        loadRelations();
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        try {
            redisTemplate = getRedisTemplate();
            if (value != null) {
                redisTemplate.opsForValue().set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
            }
            log.debug("Put query result to redis");
        } catch (Throwable t) {
            log.error("Redis put failed", t);
        }
    }

    @Override
    public Object getObject(Object key) {
        try {
            redisTemplate = getRedisTemplate();
            log.debug("Get cached query result from redis");
            return redisTemplate.opsForValue().get(key.toString());
        } catch (Throwable t) {
            log.error("Redis get failed, fail over to db", t);
            return null;
        }
    }

    @Override
    public Object removeObject(Object key) {
        try {
            redisTemplate = getRedisTemplate();
            redisTemplate.delete(key.toString());
            log.debug("Remove cached query result from redis");
        } catch (Throwable t) {
            log.error("Redis remove failed", t);
        }
        return null;
    }


    @Override
    public void clear() {
        ReadWriteLock readWriteLock = getReadWriteLock();
        Lock lock = readWriteLock.writeLock();
        lock.lock();
        try {
            // 判断 当前缓存是否正在清空，如果正在清空，取消本次操作
            // 避免缓存出现 循环 relation，造成递归无终止，调用栈溢出
            if (clearing) {
                return;
            }
            clearing = true;
            try {
                redisTemplate = getRedisTemplate();
                Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
                if (!CollectionUtils.isEmpty(keys)) {
                    redisTemplate.delete(keys);
                }
                relations.forEach(RelativeCache::clear);
                log.debug("Clear all the cached query result from redis");
            } finally {
                clearing = false;
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public int getSize() {
        redisTemplate = getRedisTemplate();
        Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
        if (keys != null) {
            return keys.size();
        } else {
            return 0;
        }
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    public void addRelation(RelativeCache relation) {
        if (relations.contains(relation)){
            return;
        }
        relations.add(relation);
    }

    void loadRelations() {
        // 加载 其他缓存更新时 需要更新此缓存的 caches
        // 将 此缓存 加入至这些 caches 的 relations 中
        List<RelativeCache> to = RelativeCacheContext.UN_LOAD__RELATIVE_CACHES_MAP.get(mapperClass);
        if (to != null) {
            to.forEach(this::addRelation);
        }

        CacheRelations annotation = AnnotationUtils.findAnnotation(mapperClass, CacheRelations.class);
        if (annotation == null) {
            return;
        }

        Class<?>[] toMappers = annotation.value();

        if (toMappers != null) {
            for (Class c : toMappers) {
                RelativeCache relativeCache = RelativeCacheContext.MAPPER_CACHE_MAP.get(c);
                if (relativeCache != null) {
                    // 将找到的缓存添加到当前缓存的relations中
                    this.addRelation(relativeCache);
                } else {
                    // 如果找不到 to cache，证明to cache还未加载，这时需将对应关系存放到 UN_LOAD_FROM_RELATIVE_CACHES_MAP
                    // 也就是说 c 对应的 cache 需要 在 当前缓存更新时 进行更新
                    List<RelativeCache> relativeCaches = RelativeCacheContext.UN_LOAD__RELATIVE_CACHES_MAP.get(c);
                    if (relativeCaches == null) {
                        relativeCaches = new ArrayList<>();
                    }
                    relativeCaches.add(this);
                    RelativeCacheContext.UN_LOAD__RELATIVE_CACHES_MAP.put(c, relativeCaches);
                }
            }
        }
    }

    private RedisTemplate<String, Object> getRedisTemplate() {
        if (redisTemplate == null) {
            redisTemplate = SpringUtil.getBean("redisTemplate");
        }
        return redisTemplate;
    }
}